VGA-Kurs - Part #9 Hallo ihr VGA-Coder und die, die es werden wollen! Willkommen zur 9. Ausgabe von "T.C.P.'s Beginner's Guide To VGA-Coding"! Fr diesen Teil habe ich mir einiges vorgenommen, nmlich die Prinzipien der 3D-Grafik. Das dies kein leichtes Thema ist, sollte man sich im Klaren darber sein, da man das Ganze als 3D-Neuling nicht gleich beim ersten Durchlesen kapieren kann. Es kostet einfach Zeit, die Zusammenhnge verstehen und diese dann in eigene Programme umsetzen zu knnen. Deshalb rate ich euch, tippt nicht einfach nur die Sources ab, sondern versucht herauszukriegen, was dahintersteckt, oder noch besser: schreibt alles selbst! Aber genug der Vorrede, lasset uns das heie 3D-Eisen anpacken! Ich hoffe, jeder von euch kann sich einen dreidimensionalen Raum vorstellen. Zum Beispiel das Weltall. Nehmen wir einmal an, unsere Sonne wre der Mittelpunkt des Weltalls. Ihre Position wird also durch die Koordinaten 0,0,0 (X-, Y- und Z-Koordinate) definiert. Auerdem nehmen wir an, da das Weltall 3 Achsen hat, die sich im Mittelpunkt der Sonne treffen. Die erste Achse (X) verluft geradewegs horizontal von links nach rechts durchs komplette All und durch die Sonne. Die zweite (Y) verluft vertikal von unten nach oben, und die dritte (Z) von uns (dem Betrachter) aus direkt durch die Sonne ins tiefe All. Als Einheiten fr die Achsen nehmen wir Lichtjahre (weils zum Beispiel pat). Jetzt denken wir uns irgendwo im Raum einen einsamen Stern. Um seine Position zu definieren, mssen wir seine Entfernung zum Nullpunkt (der Sonne) bestimmen. Angenommen, er liegt 7 Lichtjahre "rechts" von der Sonne, dann hat er die X-Koordinate 7. Liegt er zudem noch 2 Lichtjahre unterhalb der Sonne, ist seine Y-Koordinate -2, denn die Achse fhrt nach oben. Dann liegt er noch 5 Lichtjahre weiter weg von uns als die Sonne, hat also die Z-Koordinate 5. Knnt ihr euch jetzt ungefhr vorstellen, in welcher Relation sich der Stern zur Sonne und unserem Auge (dem Sichtpunkt) befindet? Gut, dann ist euch das Wichtigste schon klar. Eine Umsetzung dieses Prinzips finden wir in den uns allen bekannten Sternenflugeffekten aus Demos oder Windows-Bildschirmschonern. Hier fliegt der Betrachter sozusagen durchs Weltall, wobei sich alle Sterne an ihm vorbeibewegen. Diesen Effekt werden wir heute besprechen. Zuallererst mssen wir die Positionen der Sterne definieren. Dazu bedienen wir uns eines Records: type Star3D = record x,y,z : integer; { X-, Y- und Z-Koordinaten eines Sterns } end; Jetzt noch ein Array, in dem die Positionen aller Sterne gespeichert werden. const StarNo = 1000; { 1000 Sterne } var Stars3D : array[1..StarNo] of Star3D; { Array mit allen Koordinaten } Nun, mit 1000 Sternen haben wir ein sehr eingeschrnktes Universum, aber wie bei einem normalen Scrolly auch, werden die Sterne einfach geloopt, d.h. die Sterne, an denen der Betrachter vorbeigezogen ist, werden ans Ende der Schleife wiederangehngt. Achtung, jetzt kommt der Trick: Eigentlich mte sich im logischen Sinne der Betrachter fortbewegen, und die Sterne immer am selben Ort, also an denselben Koordinaten, verweilen. Aber der Einfachheit halber drehen wir den Spie um: Wir definieren eine Variable ZAdd, die bei jedem Schleifendurchlauf vor der Darstellung der Sterne auf die Z-Koordinaten dieser addiert wird. Ist diese Variable negativ, bewegen sich die Sterne entgegengesetzt der Z-Achse, und damit auf den Betrachter zu, wodurch der Eindruck entsteht, er bewege sich vorwrts. Dabei definieren wir die Position des Betrachters der Einfachheit wegen als 0,0,0. Das einzige, was wir dabei beachten mssen ist, da kein Stern die X- und Y-Koordinaten 0,0 hat, weil wir sonst mit ihm kollidieren wrden. Alles klar? Nein? Dann das Ganze nochmal als Pseudocode. begin Zufllige_Positionen_fr_Sterne_berechnen; VGA_Modus_setzen; Palette_setzen; repeat Sterne_weiterbewegen_durch_addieren_von_ZAdd; Alte_Sternpositionen_sichern; Sternenkoordinaten_umrechnen; Auf_Vertical_Retrace_warten; Alle_Sterne_lschen; Sterne_darstellen; until keypressed; readkey; Textmodus_setzen; end. Aber jetzt zum Wichtigsten: der Darstellung. Wie berechne ich aus den X-, Y- und Z-Koordinaten eines Sterns (3D) die X- und Y-Koordinaten, die er auf dem Bildschirm (2D) haben mu? Eigentlich ganz einfach: Um X2D zu erhalten, teilen wir X3D durch Z3D, und fr Y2D lautet die Formel Y3D durch Z3D. Warum das so ist, hat folgenden Grund. Stellen wir uns einmal eine richtig lange, gerade Strae vor, keine einzige Kurve, bis zum Horizont (der Traum fr jeden Biker ;). Am Straenrand stehen, wie hierzulande blich, Straenpfhle im Abstand von 50 Metern. Diese Pfhle stehen alle in einer Linie, haben also alle dieselbe X-Koordinate. Auch die Y-Koordinate ist bei allen gleich, denn die Strae verluft vllig eben. Nur die Z-Koordinate unterscheidet sich jeweils um 50 Meter. Vergleichen wir nun zwei dieser Pfhle auf der linken Straenseite, so fallen uns zwei Unterschiede auf. Erstens erscheint der hintere kleiner und zweitens ist er weiter rechts und weiter oben in unserem Blickfeld. Die erste Erkenntnis hilft uns bei unseren Sternen nicht viel, denn sie sind alle gleich gro (1 Pixel), aber aus der zweiten knnen wir ersehen, da die Formel von vorhin richtig war. Je weiter ein Stern von uns entfernt ist, desto grer ist seine Z-Koordinate, teilen wir als X und Y durch Z, werden diese kleiner, wie auch in Wirklichkeit. Ist das nicht cool? 3D kann so einfach sein! So wird die Variable zum Speichern der umgerechneten Koordinaten definiert: type Star2D = record x,y : integer; end; Star2DArray = array[1..StarNo] of Star2D; var Stars2D : Star2DArray; Backup : Star2DArray; Wozu das Backup? Das hat Geschwindigkeitsgrnde. Auf einem Computer unter Pentium ist nicht genug Zeit da, um zwischen dem Lschen der Sterne und dem Darstellen der Sterne die neuen Sternpositionen zu berechnen, ohne dabei mit dem Vertical Retrace in Konflikt zu kommen. Also werden die alten Sternpositionen in Backup gesichert, und vor der Darstellung die neuen berechnet. Die fertige Prozedur zum Umrechnen von 3D in 2D mte so aussehen: procedure Calc_2Dto3D; var n : integer; begin for n := 1 to StarNo do begin Stars2D[n].x := Stars3D[n].x * 128 div Stars3D[n].z + 160; Stars2D[n].y := Stars3D[n].y * 128 div Stars3D[n].z + 100; end; end; Der Formel wurde noch ein '* 128' zugefgt, weil sonst die Werte fr den Bildschirm zu klein sind, auerdem wird 160 bzw. 100 addiert, um die Mitte des Koordinatensystems in die Mitte des Screens zu verlagern. Eins fehlt uns jetzt noch zur perfekten (naja) 3D-Illusion. Die weiter entfernten Sterne sollten dunkler dargestellt werden als die, die dem Betrachter am nahsten sind. Das drfte auch kein Problem sein, wenn wir die Sterne einfach je dunkler zeichnen, desto grer ihre Z-Koordinate ist. Dies ginge mit der Formel Farbe = Star3D.z div 10. Dies pat allerdings nur, wenn man StarNo auf 1000 lt, ansonsten mu man den Wert 10 etwas erhhen. Das Ergebnis der Formel mu dann von 63 abgezogen werden, weil unsere Palette in 64 Graustufen von Schwarz nach Wei verluft. Auerdem werden nur Sterne dargestellt, deren Z-Koordinate < 500 ist, dies lt den Effekt etwas realistischer wirken. Hier also die Prozeduren zum Darstellen, Lschen und Weiterbewegen der Sterne. procedure DrawStars; var n : integer; begin for n := 1 to StarNo do if (Stars2D[n].x > 0) and (Stars2D[n].x < 320) and (Stars2D[n].y > 0) and (Stars2D[n].y < 200) and (Stars3D[n].z < 500) then mem[$A000:Stars2D[n].y*320+Stars2D[n].x] := 63-Stars3D[n].z div 10; end; procedure ClearStars; var n : integer; begin for n := 1 to StarNo do if (Backup[n].x > 0) and (Backup[n].x < 320) and (Backup[n].y > 0) and (Backup[n].y < 200) then mem[$A000:Backup[n].y*320+Backup[n].x] := 0; end; procedure MoveStars; var n : integer; begin for n := 1 to StarNo do begin inc(Stars3D[n].z,ZAdd); if Stars3D[n].z < 1 then inc(Stars3D[n].z,StarNo); if Stars3D[n].z > StarNo then dec(Stars3D[n].z,StarNo); end; end; Das war's eigentlich schon. Was uns noch fehlt, ist die Prozedur zum Erstellen des Sternen-Arrays. Das drfte jetzt aber kein Problem mehr darstellen, oder? procedure InitStars; var n : integer; begin for n := 1 to StarNo do begin repeat Stars3D[n].x := random(640) - 320; Stars3D[n].y := random(400) - 200; until (Stars3D[n].x <> 0) and (Stars3D[n].y <> 0); Stars3D[n].z := random(StarNo); end; end; Und schon haben wir alles zusammen was wir bentigen. Ach, ihr wollt auch noch das fertige Programm? Na gut, ich will euch ja nicht zu sehr fordern ;) program Sternen_Effekt; uses crt; const StarNo = 1000; { 1000 Sterne } type Star3D = record x,y,z : integer; end; Star2D = record x,y : integer; end; Star2DArray = array[1..StarNo] of Star2D; var Stars3D : array[1..StarNo] of Star3D; Stars2D : Star2DArray; Backup : Star2DArray; ZAdd,n : integer; { Hier Calc_3Dto2D, DrawStars, ClearStars und MoveStars einfgen } procedure WaitRetrace;assembler; asm mov dx,3DAh @1: in al,dx and al,8 jz @1 @2: in al,dx and al,8 jz @2 end; procedure SetPal(col,r,g,b:byte);assembler; asm mov dx,3C8h mov al,col out dx,al inc dx mov al,r out dx,al mov al,g out dx,al mov al,b out dx,al end; begin randomize; { Zufallsgenerator anwerfen } InitStars; { Sternenarrays mit Werten fllen } asm mov ax,13h; int 10h end; { VGA Modus setzen } for n := 0 to 63 do SetPal(n,n,n,n); { Palette setzen } ZAdd := -4; { ZAdd initialisieren } repeat MoveStars; { Sterne bewegen } Backup := Stars2D; { Alte Sternpositionen sichern } Calc_2Dto3D; { und neue berechnen } WaitRetrace; { Auf Retrace warten } ClearStars; { Alte Sterne lschen } DrawStars; { und neue zeichnen } until keypressed; readkey; asm mov ax,3; int 10h end; end. Die Variable ZAdd bestimmt die Geschwindigkeit der Sterne. Ist sie positiv, bewegen sie sich vom Betrachter weg. Und wieder habt ihr eine (hoffentlich nicht zu schwere) Ausgabe meines VGA-Kurses berstanden. Fr den nchsten Teil plane ich entweder die Fortsetzung des 3D-Kurses oder einen Kurs ber SVGA-Coding (dafr gab es immerhin 5 Anfragen im PCH 4/96), inlusive High- und True-Color-Auflsungen. Bis denn! [ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ] [ Distributed exclusively through PC-Heimwerker, Verlag Thomas Eberle. ] [ ] [ No part of this document may be reproduced, transmitted, ] [ transcribed, stored in a retrieval system, or translated into any ] [ human or computer language, in any form or by any means; electronic, ] [ mechanical, magnetic, optical, chemical, manual or otherwise, ] [ without the expressed written permission of the author. ] [ ] [ The information contained in this text is believed to be correct. ] [ The text is subject to change without notice and does not represent ] [ a commitment on the part of the author. ] [ The author does not make a warranty of any kind with regard to this ] [ material, including, but not limited to, the implied warranties of ] [ merchantability and fitness for a particular purpose. The author ] [ shall not be liable for errors contained herein or for incidental or ] [ consequential damages in connection with the furnishing, performance ] [ or use of this material. ]